/*
 * Copyright (C) 2006-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

package org.alfresco.jlan.server.auth.passthru;

import java.io.IOException;

import org.alfresco.jlan.netbios.NetBIOSSession;
import org.alfresco.jlan.netbios.NetworkSession;
import org.alfresco.jlan.netbios.RFCNetBIOSProtocol;
import org.alfresco.jlan.smb.PacketType;
import org.alfresco.jlan.smb.SMBException;
import org.alfresco.jlan.smb.SMBStatus;
import org.alfresco.jlan.util.DataPacker;

/**
 *  SMB packet type class
 *
 * @author gkspencer
 */
public class SMBPacket {

  //	SMB packet offsets, assuming an RFC NetBIOS transport

  public static final int SIGNATURE 		= RFCNetBIOSProtocol.HEADER_LEN;
  public static final int COMMAND 			= 4 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int ERRORCODE 		= 5 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int ERRORCLASS 		= 5 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int ERROR 				= 7 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int FLAGS 				= 9 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int FLAGS2 				= 10 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int PIDHIGH 			= 12 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int SID 					= 18 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int SEQNO 				= 20 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int TID 					= 24 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int PID 					= 26 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int UID 					= 28 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int MID 					= 30 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int WORDCNT 			= 32 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int ANDXCOMMAND 	= 33 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int ANDXRESERVED 	= 34 + RFCNetBIOSProtocol.HEADER_LEN;
  public static final int PARAMWORDS 		= 33 + RFCNetBIOSProtocol.HEADER_LEN;

  //	SMB packet header length for a transaction type request

  public static final int TRANS_HEADERLEN = 66 + RFCNetBIOSProtocol.HEADER_LEN;

	//	Minimum receive length for a valid SMB packet

  public static final int MIN_RXLEN = 32;

	//	Default buffer size to allocate for SMB packets

  public static final int DEFAULT_BUFSIZE = 4096;

	//	Flag bits

  public static final int FLG_SUBDIALECT 	= 0x01;
  public static final int FLG_CASELESS 		= 0x08;
  public static final int FLG_CANONICAL 	= 0x10;
  public static final int FLG_OPLOCK 			= 0x20;
  public static final int FLG_NOTIFY 			= 0x40;
  public static final int FLG_RESPONSE 		= 0x80;

	//	Flag2 bits

  public static final int FLG2_LONGFILENAMES 	  = 0x0001;
  public static final int FLG2_EXTENDEDATTRIB   = 0x0002;
  public static final int FLG2_SECURITYSIG      = 0x0004;
  public static final int FLG2_EXTENDEDSECURITY = 0x0800;
  public static final int FLG2_READIFEXE 			  = 0x2000;
  public static final int FLG2_LONGERRORCODE 	  = 0x4000;
  public static final int FLG2_UNICODE 				  = 0x8000;

	//	Security mode bits

  public static final int SEC_USER 		= 0x0001;
  public static final int SEC_ENCRYPT = 0x0002;

	//	Raw mode bits

  public static final int RAW_READ 	= 0x0001;
  public static final int RAW_WRITE = 0x0002;

	//	SMB packet buffer

  private byte[] m_smbbuf;

	//	Packet type

  private int m_pkttype;

	//	Current byte area pack/unpack position

	protected int m_pos;
	protected int m_endpos;

	//	Time of last packet send

	protected long m_lastTxTime;

  /**
   * Default constructor
   */
  public SMBPacket() {
    m_smbbuf = new byte[DEFAULT_BUFSIZE];
    InitializeBuffer();
  }

  /**
   * Construct an SMB packet using the specified packet buffer.
   *
   * @param buf    SMB packet buffer.
   */
  public SMBPacket(byte[] buf) {
    m_smbbuf = buf;
  }

  /**
   * Construct an SMB packet of the specified size.
   *
   * @param siz    Size of SMB packet buffer to allocate.
   */
  public SMBPacket(int siz) {
    m_smbbuf = new byte[siz];
    InitializeBuffer();
  }

	/**
	 * Check if a received SMB is valid, if not then throw an exception
	 *
	 * @exception SMBException
	 */
	public final void checkForError()
		throws SMBException {

	  //  Check if a valid SMB response has been received

	  if (isValidResponse() == false) {

	  	//	Check for NT error codes

	  	if ( isLongErrorCode())
	  		throw new SMBException(SMBStatus.NTErr, getLongErrorCode());
	  	else
	  		throw new SMBException(getErrorClass(), getErrorCode());
	  }
	}

  /**
   * Clear the data byte count
   */
  public final void clearBytes() {
    int offset = getByteOffset() - 2;
    DataPacker.putIntelShort( 0, m_smbbuf, offset);
  }

	/**
	 * Check if the error class/code match the specified error/class
	 *
	 * @param errClass int
	 * @param errCode int
	 * @return boolean
	 */
	public final boolean equalsError(int errClass, int errCode) {
		if ( getErrorClass() == errClass && getErrorCode() == errCode)
			return true;
		return false;
	}

  /**
   * Send the SMB packet and receive the response packet
   *
   * @param sess       Network session to send/receive the packet over.
   * @param rxPkt      SMB packet to receive the response into.
   * @param throwerr   If true then throw an I/O error if an invalid response is
   *                   received.
   * @exception        java.io.IOException   If a network error occurs.
   * @exception				 SMBException					 If an SMB level error occurs
   */
  protected final synchronized void ExchangeLowLevelSMB(NetworkSession sess, SMBPacket rxPkt, boolean throwerr)
    throws java.io.IOException, SMBException {

		//	Set multiplex id

		if ( getMultiplexId() == 0)
			setMultiplexId(1);

    //	Send the SMB request

    sess.Send(m_smbbuf, getLength());

		//	Receive a response

    if (sess.Receive(rxPkt.getBuffer()) >= MIN_RXLEN) {

			//	Check if the response is for the current request

			if ( rxPkt.getCommand() == m_pkttype) {

	      //  Check if a valid SMB response has been received

	      if (throwerr == true)
	      	checkForError();

				//	Valid packet received, return to caller

				return;
			}
    }

    //	Invalid receive packet

    throw new java.io.IOException("Invalid SMB Receive Packet");
  }

  /**
   * Send/receive an SMB protocol packet to the remote server.
   *
   * @param sess   SMB session to send/receive data on.
   * @param rxPkt  SMB packet to receive the response into.
   * @exception    java.io.IOException   If an I/O error occurs.
   * @exception 	 SMBException If an SMB level error occurs.
   */
  public synchronized final void ExchangeSMB( AuthenticateSession sess, SMBPacket rxPkt)
  	throws SMBException, IOException {

    //  Call the main SMB exhchange method

    ExchangeSMB(sess, rxPkt, false);
  }

  /**
   * Send the SMB packet and receive the response packet
   *
   * @param sess       SMB session to send/receive the packet over.
   * @param rxPkt      SMB packet to receive the response into.
   * @param throwerr   If true then throw an I/O error if an invalid response is
   *                   received.
   * @exception    		 java.io.IOException   If an I/O error occurs.
   * @exception        SMBException   If an SMB level error occurs.
   */
  public synchronized final void ExchangeSMB( AuthenticateSession sess, SMBPacket rxPkt, boolean throwerr)
    throws SMBException, IOException {

		//	Set the process id, user id and multiplex id

		setProcessId(sess.getProcessId());
		setUserId(sess.getUserId());

		if ( getMultiplexId() == 0)
			setMultiplexId(1);

		//	Get the network session

		NetworkSession netSess = sess.getSession();

    //	Send the SMB request

    netSess.Send(m_smbbuf, getLength());

		//	Receive the response, other asynchronous responses may be received before the response for this request

		boolean rxValid = false;

		while ( rxValid == false) {

			//	Receive a response

	    if (netSess.Receive(rxPkt.getBuffer()) >= MIN_RXLEN) {

				//	Check if the response is for the current request

				if ( rxPkt.getCommand() == m_pkttype) {

		      //  Check if a valid SMB response has been received

		      if (throwerr == true)
		      	checkForError();

					//	Valid packet received, return to caller

					return;
				}
	    }
    }

    //	Invalid receive packet

    throw new java.io.IOException("Invalid SMB Receive Packet");
  }

  /**
   * Get the secondary command code
   *
   * @return   Secondary command code
   */
  public final int getAndXCommand() {
    return m_smbbuf[ANDXCOMMAND] & 0xFF;
  }

  /**
   * Return the byte array used for the SMB packet
   *
   * @return   Byte array used for the SMB packet.
   */
  public final byte[] getBuffer() {
    return m_smbbuf;
  }

  /**
   * Return the total buffer size available to the SMB request
   *
   * @return   Total SMB buffer length available.
   */
  public final int getBufferLength() {
    return m_smbbuf.length - RFCNetBIOSProtocol.HEADER_LEN;
  }

	/**
	 * Return the available buffer space for data bytes
	 *
	 * @return int
	 */
	public final int getAvailableLength() {
		return m_smbbuf.length - DataPacker.longwordAlign(getByteOffset());
	}

  /**
   * Get the data byte count for the SMB packet
   *
   * @return     Data byte count
   */
  public final int getByteCount() {

    //	Calculate the offset of the byte count

    int pos = PARAMWORDS + (2 * getParameterCount());
    return DataPacker.getIntelShort(m_smbbuf, pos);
  }

  /**
   * Get the data byte area offset within the SMB packet
   *
   * @return   Data byte offset within the SMB packet.
   */
  public final int getByteOffset() {

    //	Calculate the offset of the byte buffer

    int pCnt = getParameterCount();
    int pos = WORDCNT + (2 * pCnt) + 3;
    return pos;
  }

  /**
   * Get the SMB command
   *
   * @return   SMB command code.
   */
  public final int getCommand() {
    return m_smbbuf[COMMAND] & 0xFF;
  }

	/**
	 * Determine if normal or long error codes have been returned
	 *
	 * @return boolean
	 */
	public final boolean hasLongErrorCode() {
	  if ((getFlags2() & FLG2_LONGERRORCODE) == 0)
	  	return false;
	  return true;
	}

	/**
	 * Return the saved packet type
	 *
	 * @return int
	 */
	public final int isType() {
		return m_pkttype;
	}

	/**
	 * Check if the packet contains ASCII or Unicode strings
	 *
	 * @return boolean
	 */
	public final boolean isUnicode() {
	  return (getFlags2() & FLG2_UNICODE) != 0 ? true : false;
	}

	/**
	 * Check if the packet is using caseless filenames
	 *
	 * @return boolean
	 */
	public final boolean isCaseless() {
	  return (getFlags() & FLG_CASELESS) != 0 ? true : false;
	}

	/**
	 * Check if long file names are being used
	 *
	 * @return boolean
	 */
	public final boolean isLongFileNames() {
	  return (getFlags2() & FLG2_LONGFILENAMES) != 0 ? true : false;
	}

	/**
	 * Check if long error codes are being used
	 *
	 * @return boolean
	 */
	public final boolean isLongErrorCode() {
	  return (getFlags2() & FLG2_LONGERRORCODE) != 0 ? true : false;
	}

  /**
   * Get the SMB error class
   *
   * @return   SMB error class.
   */
  public final int getErrorClass() {
    return m_smbbuf[ERRORCLASS] & 0xFF;
  }

  /**
   * Get the SMB error code
   *
   * @return   SMB error code.
   */
  public final int getErrorCode() {
    return m_smbbuf[ERROR] & 0xFF;
  }

  /**
   * Get the SMB flags value.
   *
   * @return   SMB flags value.
   */
  public final int getFlags() {
    return m_smbbuf[FLAGS] & 0xFF;
  }

  /**
   * Get the SMB flags2 value.
   *
   * @return   SMB flags2 value.
   */
  public final int getFlags2() {
    return DataPacker.getIntelShort(m_smbbuf, FLAGS2);
  }

  /**
   * Calculate the total used packet length.
   *
   * @return   Total used packet length.
   */
  public final int getLength() {
    return (getByteOffset() + getByteCount()) - SIGNATURE;
  }

  /**
   * Get the long SMB error code
   *
   * @return   Long SMB error code.
   */
  public final int getLongErrorCode() {
    return DataPacker.getIntelInt(m_smbbuf, ERRORCODE);
  }

  /**
   * Get the multiplex identifier.
   *
   * @return   Multiplex identifier.
   */
  public final int getMultiplexId() {
    return DataPacker.getIntelShort(m_smbbuf, MID);
  }

  /**
   * Get a parameter word from the SMB packet.
   *
   * @param idx    Parameter index (zero based).
   * @return       Parameter word value.
   * @exception    java.lang.IndexOutOfBoundsException   If the parameter index is out of range.
   */
  public final int getParameter(int idx) throws java.lang.IndexOutOfBoundsException {

    //	Range check the parameter index

    if (idx > getParameterCount())
      throw new java.lang.IndexOutOfBoundsException();

    //	Calculate the parameter word offset

    int pos = WORDCNT + (2 * idx) + 1;
    return DataPacker.getIntelShort(m_smbbuf, pos) & 0xFFFF;
  }

  /**
   * Get the specified parameter words, as an int value.
   *
   * @param idx    Parameter index (zero based).
   * @return int
   */
  public final int getParameterLong(int idx) {
    int pos = WORDCNT + (2 * idx) + 1;
    return DataPacker.getIntelInt(m_smbbuf, pos);
  }

  /**
   * Get the parameter count
   *
   * @return   Parameter word count.
   */
  public final int getParameterCount() {
    return m_smbbuf[WORDCNT];
  }

  /**
   * Get the process indentifier (PID)
   *
   * @return     Process identifier value.
   */
  public final int getProcessId() {
    return DataPacker.getIntelShort(m_smbbuf, PID);
  }

  /**
   * Get the tree identifier (TID)
   *
   * @return   Tree identifier (TID)
   */
  public final int getTreeId() {
    return DataPacker.getIntelShort(m_smbbuf, TID);
  }

  /**
   * Get the user identifier (UID)
   *
   * @return   User identifier (UID)
   */
  public final int getUserId() {
    return DataPacker.getIntelShort(m_smbbuf, UID);
  }

  /**
   * Return the last sent packet time
   *
   * @return long
   */
  public final long getLastPacketSendTime() {
    return m_lastTxTime;
  }

  /**
   * Initialize the SMB packet buffer.
   */
  private final void InitializeBuffer() {

    //	Set the packet signature

    m_smbbuf[SIGNATURE] = (byte) 0xFF;
    m_smbbuf[SIGNATURE + 1] = (byte) 'S';
    m_smbbuf[SIGNATURE + 2] = (byte) 'M';
    m_smbbuf[SIGNATURE + 3] = (byte) 'B';
  }

  /**
   * Determine if this packet is an SMB response, or command packet
   *
   * @return     true if this SMB packet is a response, else false
   */
  public final boolean isResponse() {
    int resp = getFlags();
    if ((resp & FLG_RESPONSE) != 0)
      return true;
    return false;
  }

  /**
   * Check if the response packet is valid, ie. type and flags
   *
   * @return     true if the SMB packet is a response packet and the response is
   *             valid, else false.
   */
  public final boolean isValidResponse() {

    //	Check if this is a response packet, and the correct type of packet

    if (isResponse() && getCommand() == m_pkttype) {

      //	Check if standard error codes or NT 32-bit error codes are being used

      if (( getFlags2() & FLG2_LONGERRORCODE) == 0) {
      	if ( getErrorCode() == SMBStatus.Success)
      		return true;
      }
     	else if ( getLongErrorCode() == SMBStatus.NTSuccess)
      	return true;
    }
    return false;
  }

	/**
	 * Pack a byte (8 bit) value into the byte area
	 *
	 * @param val byte
	 */
	public final void packByte(byte val) {
		m_smbbuf[m_pos++] = val;
	}

	/**
	 * Pack a byte (8 bit) value into the byte area
	 *
	 * @param val int
	 */
	public final void packByte(int val) {
		m_smbbuf[m_pos++] = (byte) val;
	}

	/**
	 * Pack the specified bytes into the byte area
	 *
	 * @param byts byte[]
	 * @param len int
	 */
	public final void packBytes(byte[] byts, int len) {
		System.arraycopy(byts,0,m_smbbuf,m_pos,len);
		m_pos += len;
	}

	/**
	 * Pack a string using either ASCII or Unicode into the byte area
	 *
	 * @param str String
	 * @param uni boolean
	 */
	public final void packString(String str, boolean uni) {

		//	Check for Unicode or ASCII

		if ( uni) {

			//	Word align the buffer position, pack the Unicode string

			m_pos = DataPacker.wordAlign(m_pos);
			DataPacker.putUnicodeString(str,m_smbbuf,m_pos,true);
			m_pos += (str.length() * 2) + 2;
		}
		else {

			//	Pack the ASCII string

			DataPacker.putString(str,m_smbbuf,m_pos,true);
			m_pos += str.length() + 1;
		}
	}

	/**
	 * Pack a word (16 bit) value into the byte area
	 *
	 * @param val int
	 */
	public final void packWord(int val) {
		DataPacker.putIntelShort(val, m_smbbuf, m_pos);
		m_pos += 2;
	}

	/**
	 * Pack a 32 bit integer value into the byte area
	 *
	 * @param val int
	 */
	public final void packInt(int val) {
		DataPacker.putIntelInt(val,m_smbbuf,m_pos);
		m_pos += 4;
	}

	/**
	 * Pack a long integer (64 bit) value into the byte area
	 *
	 * @param val long
	 */
	public final void packLong(long val) {
		DataPacker.putIntelLong(val,m_smbbuf,m_pos);
		m_pos += 8;
	}

	/**
	 * Return the current byte area buffer position
	 *
	 * @return int
	 */
	public final int getPosition() {
		return m_pos;
	}

	/**
	 * Set the byte area buffer position
	 *
	 * @param pos int
	 */
	public final void setPosition(int pos) {
		m_pos = pos;
	}

	/**
	 * Unpack a byte value from the byte area
	 *
	 * @return int
	 */
	public final int unpackByte() {
		return (int) m_smbbuf[m_pos++];
	}

	/**
	 * Unpack a block of bytes from the byte area
	 *
	 * @param len int
	 * @return byte[]
	 */
	public final byte[] unpackBytes(int len) {
		if ( len <= 0)
			return null;

		byte[] buf = new byte[len];
		System.arraycopy(m_smbbuf,m_pos,buf,0,len);
		m_pos += len;
		return buf;
	}

	/**
	 * Unpack a word (16 bit) value from the byte area
	 *
	 * @return int
	 */
	public final int unpackWord() {
		int val = DataPacker.getIntelShort(m_smbbuf,m_pos);
		m_pos += 2;
		return val;
	}

	/**
	 * Unpack an integer (32 bit) value from the byte/parameter area
	 *
	 * @return int
	 */
	public final int unpackInt() {
		int val = DataPacker.getIntelInt(m_smbbuf,m_pos);
		m_pos += 4;
		return val;
	}

	/**
	 * Unpack a long integer (64 bit) value from the byte area
	 *
	 * @return long
	 */
	public final long unpackLong() {
		long val = DataPacker.getIntelLong(m_smbbuf,m_pos);
		m_pos += 8;
		return val;
	}

	/**
	 * Unpack a string from the byte area
	 *
	 * @param uni boolean
	 * @return String
	 */
	public final String unpackString(boolean uni) {

		//	Check for Unicode or ASCII

		String ret = null;

		if ( uni) {

			//	Word align the current buffer position

			m_pos = DataPacker.wordAlign(m_pos);
			ret = DataPacker.getUnicodeString(m_smbbuf,m_pos,255);
			if ( ret != null)
				m_pos += ( ret.length() * 2) + 2;
		}
		else {

			//	Unpack the ASCII string

			ret = DataPacker.getString(m_smbbuf,m_pos,255);
			if ( ret != null)
				m_pos += ret.length() + 1;
		}

		//	Return the string

		return ret;
	}

	/**
	 * Unpack a string from the byte area
	 *
	 * @param len int
	 * @param uni boolean
	 * @return String
	 */
	public final String unpackString(int len, boolean uni) {

		//	Check for Unicode or ASCII

		String ret = null;

		if ( uni) {

			//	Word align the current buffer position

			m_pos = DataPacker.wordAlign(m_pos);
			ret = DataPacker.getUnicodeString(m_smbbuf,m_pos,len);
			if ( ret != null)
				m_pos += ( ret.length() * 2);
		}
		else {

			//	Unpack the ASCII string

			ret = DataPacker.getString(m_smbbuf,m_pos,len);
			if ( ret != null)
				m_pos += ret.length();
		}

		//	Return the string

		return ret;
	}

	/**
	 * Check if there is more data in the byte area
	 *
	 * @return boolean
	 */
	public final boolean hasMoreData() {
		if ( m_pos < m_endpos)
			return true;
		return false;
	}

  /**
   * Receive an SMB response packet.
   *
   * @param sess   NetBIOS session to receive the SMB packet on.
   * @exception    java.io.IOException   If an I/O error occurs.
   */
  private final void ReceiveSMB(NetBIOSSession sess)
    throws java.io.IOException {

    if (sess.Receive(m_smbbuf) >= MIN_RXLEN)
      return;

    //	Not enough data received for an SMB header

    throw new java.io.IOException("Short NetBIOS receive");
  }

  /**
   * Receive an SMB packet on the spceified SMB session.
   *
   * @param sess   SMB session to receive the packet on.
   * @exception    java.io.IOException   If a network error occurs
   * @exception 	 SMBException		If an SMB level error occurs
   */
  protected final void ReceiveSMB( AuthenticateSession sess)
  	throws java.io.IOException, SMBException {

  	//	Call the main receive method

  	ReceiveSMB(sess, true);
  }

  /**
   * Receive an SMB packet on the spceified SMB session.
   *
   * @param sess   		SMB session to receive the packet on.
   * @param throwErr	Flag to indicate if an error is thrown if an error response is received
   * @exception    		java.io.IOException   If a network error occurs
   * @exception 	 		SMBException		If an SMB level error occurs
   */
  protected final void ReceiveSMB( AuthenticateSession sess, boolean throwErr)
  	throws java.io.IOException, SMBException {

		//	Get the network session

		NetworkSession netSess= sess.getSession();

		//	Receive the response, other asynchronous responses may be received before the response for this request

		boolean rxValid = false;

		while ( rxValid == false) {

			//	Receive a response

	    if (netSess.Receive(getBuffer()) >= MIN_RXLEN) {

				//	Check if the response is for the current request

				if ( getCommand() == m_pkttype) {

		      //  Check if a valid SMB response has been received

					if ( throwErr == true)
						checkForError();

					//	Valid packet received, return to caller

					return;
				}

				//	Asynchronous response received, pass the packet to the session for processing

				sess.processAsynchResponse(this);
	    }
	    else {

		    //	Not enough data received for an SMB header

		    throw new java.io.IOException("Short NetBIOS receive");
	    }
    }
  }

  /**
   * Receive an asynchronous SMB response from the server
   *
   * @param sess Session
   * @param waitTime 		Receive timeout in milliseconds, or zero for no timeout
   * @throws java.io.IOException
   * @throws SMBException
   */
  protected final void ReceiveAsynchSMB( AuthenticateSession sess, int waitTime)
		throws java.io.IOException, SMBException {

		//	Get the network session

		NetworkSession netSess = sess.getSession();
    netSess.setTimeout( waitTime);

		//	Receive, or wait for, a response

    if (netSess.Receive(getBuffer()) >= MIN_RXLEN) {

			//	Asynchronous response received, pass the packet to the session for processing

			sess.processAsynchResponse(this);
    }
    else {

	    //	Not enough data received for an SMB header

	    throw new java.io.IOException("Short NetBIOS receive");
    }
	}

  /**
   * Send the SMB packet on the specified SMB session.
   *
   * @param sess   SMB session to send this packet over.
   * @exception    java.io.IOException   If an I/O error occurs.
   */
  protected final void SendSMB( AuthenticateSession sess) throws java.io.IOException {

    //	Update the last send time

    m_lastTxTime = System.currentTimeMillis();

    //	Send the SMB request

    sess.getSession().Send(m_smbbuf, getLength());
  }

  /**
   * Set the secondary SMB command
   *
   * @param cmd    Secondary SMB command code.
   */
  public final void setAndXCommand(int cmd) {

  	//	Set the chained command packet type

    m_smbbuf[ANDXCOMMAND] = (byte) cmd;
    m_smbbuf[ANDXRESERVED] = (byte) 0;

    //	If the AndX command is disabled clear the offset to the chained packet

    if ( cmd == PacketType.NoChainedCommand)
    	setParameter(1, 0);
  }

  /**
   * Set the data byte count for this SMB packet
   *
   * @param cnt    Data byte count.
   */
  public final void setByteCount(int cnt) {
    int offset = getByteOffset() - 2;
    DataPacker.putIntelShort( cnt, m_smbbuf, offset);
  }

  /**
   * Set the data byte count for this SMB packet
   */

  public final void setByteCount() {
    int offset = getByteOffset() - 2;
    int len = m_pos - getByteOffset();
    DataPacker.putIntelShort( len, m_smbbuf, offset);
  }

  /**
   * Set the data byte area in the SMB packet
   *
   * @param byts   Byte array containing the data to be copied to the SMB packet.
   */
  public final void setBytes(byte[] byts) {
    int offset = getByteOffset() - 2;
    DataPacker.putIntelShort( byts.length, m_smbbuf, offset);

    offset += 2;

    for (int idx = 0; idx < byts.length; m_smbbuf[offset + idx] = byts[idx++]);
  }

  /**
   * Set the SMB command
   *
   * @param cmd    SMB command code
   */
  public final void setCommand(int cmd) {
    m_pkttype = cmd;
    m_smbbuf[COMMAND] = (byte) cmd;
  }

  /**
   * Set the SMB error class.
   *
   * @param cl     SMB error class.
   */
  public final void setErrorClass(int cl) {
    m_smbbuf[ERRORCLASS] = (byte) (cl & 0xFF);
  }

  /**
   * Set the SMB error code
   *
   * @param sts    SMB error code.
   */
  public final void setErrorCode(int sts) {
    m_smbbuf[ERROR] = (byte) (sts & 0xFF);
  }

  /**
   * Set the SMB flags value.
   *
   * @param flg    SMB flags value.
   */
  public final void setFlags(int flg) {
    m_smbbuf[FLAGS] = (byte) flg;
  }

  /**
   * Set the SMB flags2 value.
   *
   * @param flg    SMB flags2 value.
   */
  public final void setFlags2(int flg) {
    DataPacker.putIntelShort( flg, m_smbbuf, FLAGS2);
  }

  /**
   * Set the multiplex identifier.
   *
   * @param mid    Multiplex identifier
   */
  public final void setMultiplexId(int mid) {
    DataPacker.putIntelShort( mid, m_smbbuf, MID);
  }

  /**
   * Set the specified parameter word.
   *
   * @param idx    Parameter index (zero based).
   * @param val    Parameter value.
   */
  public final void setParameter(int idx, int val) {
    int pos = WORDCNT + (2 * idx) + 1;
    DataPacker.putIntelShort( val, m_smbbuf, pos);
  }

  /**
   * Set the specified parameter words.
   *
   * @param idx    Parameter index (zero based).
   * @param val    Parameter value.
   */

  public final void setParameterLong(int idx, int val) {
    int pos = WORDCNT + (2 * idx) + 1;
    DataPacker.putIntelInt(val, m_smbbuf, pos);
  }

  /**
   * Set the parameter count
   *
   * @param cnt    Parameter word count.
   */
  public final void setParameterCount(int cnt) {
    m_smbbuf[WORDCNT] = (byte) cnt;
  }

  /**
   * Set the process identifier value (PID).
   *
   * @param pid    Process identifier value.
   */
  public final void setProcessId(int pid) {
    DataPacker.putIntelShort( pid, m_smbbuf, PID);
  }

  /**
   * Set the packet sequence number, for connectionless commands.
   *
   * @param seq		Sequence number.
   */
  public final void setSeqNo(int seq) {
    DataPacker.putIntelShort( seq, m_smbbuf, SEQNO);
  }

  /**
   * Set the session id.
   *
   * @param sid		Session id.
   */
  public final void setSID(int sid) {
    DataPacker.putIntelShort( sid, m_smbbuf, SID);
  }

  /**
   * Set the tree identifier (TID)
   *
   * @param tid    Tree identifier value.
   */
  public final void setTreeId(int tid) {
    DataPacker.putIntelShort( tid, m_smbbuf, TID);
  }

  /**
   * Set the user identifier (UID)
   *
   * @param uid    User identifier value.
   */
  public final void setUserId(int uid) {
    DataPacker.putIntelShort( uid, m_smbbuf, UID);
  }

	/**
	 * Align the byte area pointer on an int (32bit) boundary
	 */
	public final void alignBytePointer() {
  	m_pos = DataPacker.longwordAlign(m_pos);
	}

  /**
   * Reset the byte/parameter pointer area for packing/unpacking data items from the packet
   */
  public final void resetBytePointer() {
  	m_pos 	 = getByteOffset();
  	m_endpos = m_pos + getByteCount();
  }

  /**
   * Reset the byte/parameter pointer area for packing/unpacking data items from the packet, and align
   * the buffer on an int (32bit) boundary
   */
  public final void resetBytePointerAlign() {
  	m_pos 	 = DataPacker.longwordAlign(getByteOffset());
  	m_endpos = m_pos + getByteCount();
  }

  /**
   * Reset the byte/parameter pointer area for packing/unpacking paramaters from the packet
   */
  public final void resetParameterPointer() {
  	m_pos = PARAMWORDS;
  }

  /**
   * Set the unpack pointer to the specified offset, for AndX processing
   *
   * @param off int
   * @param len int
   */
  public final void setBytePointer(int off, int len) {
  	m_pos 	 = off;
  	m_endpos = m_pos + len;
  }

  /**
   * Skip a number of bytes in the parameter/byte area
   *
   * @param cnt int
   */
  public final void skipBytes(int cnt) {
  	m_pos += cnt;
  }

  /**
   * Return the flags value as a string
   *
   * @return String
   */
  protected final String getFlagsAsString() {

    //	Get the flags value

    int flags = getFlags();
    if ( flags == 0)
     	return "<None>";

    StringBuffer str = new StringBuffer();
    if ((flags & FLG_SUBDIALECT) != 0)
      str.append("SubDialect,");

    if ((flags & FLG_CASELESS) != 0)
      str.append("Caseless,");

    if ((flags & FLG_CANONICAL) != 0)
      str.append("Canonical,");

    if ((flags & FLG_OPLOCK) != 0)
      str.append("Oplock,");

    if ((flags & FLG_NOTIFY) != 0)
      str.append("Notify,");

    if ((flags & FLG_RESPONSE) != 0)
      str.append("Response,");

    str.setLength(str.length() - 1);

    return str.toString();
  }

  /**
   * Return the flags2 value as a string
   *
   * @return String
   */
  protected final String getFlags2AsString() {

    //	Get the flags2 value

    int flags2 = getFlags2();

    if ( flags2 == 0)
      return "<None>";

    StringBuffer str = new StringBuffer();

    if (( flags2 & FLG2_LONGFILENAMES) != 0)
      str.append("LongFilenames,");

    if (( flags2 & FLG2_EXTENDEDATTRIB) != 0)
      str.append("ExtAttributes,");

    if (( flags2 & FLG2_READIFEXE) != 0)
      str.append("ReadIfEXE,");

    if (( flags2 & FLG2_LONGERRORCODE) != 0)
      str.append("LongErrorCode,");

    if (( flags2 & FLG2_UNICODE) != 0)
      str.append("Unicode,");

    str.setLength(str.length() - 1);

    return str.toString();
  }
}
